Optimize freshness of git repos
authorAlex Crichton <alex@alexcrichton.com>
Thu, 31 Jul 2014 19:20:05 +0000 (12:20 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 31 Jul 2014 19:20:05 +0000 (12:20 -0700)
This commit cuts down on the number of invocations of `git` to make the "happy
path" of an up-to-date git repo much smoother. The precise information is also
applied from the lockfile to speed things up.

src/cargo/sources/git/mod.rs
src/cargo/sources/git/source.rs
src/cargo/sources/git/utils.rs

index f9ec6d6682c3b6c49e038a5e66250cc81ddb0e74..8b1ba4977e590825fd0a9ccb68afe64120c884c0 100644 (file)
@@ -1,4 +1,4 @@
-pub use self::utils::{GitRemote,GitDatabase,GitCheckout};
+pub use self::utils::{GitRemote, GitDatabase, GitCheckout, GitRevision};
 pub use self::source::{GitSource, canonicalize_url};
 mod utils;
 mod source;
index 61759e65eef321b458f9928c7183a91775515614..925b9ae24b43b8d744c689571d717502fa1a1f2c 100644 (file)
@@ -155,9 +155,9 @@ impl<'a, 'b> Registry for GitSource<'a, 'b> {
 
 impl<'a, 'b> Source for GitSource<'a, 'b> {
     fn update(&mut self) -> CargoResult<()> {
-        let should_update = self.config.update_remotes() || {
-            !self.remote.has_ref(&self.db_path, self.reference.as_slice()).is_ok()
-        };
+        let actual_rev = self.remote.rev_for(&self.db_path,
+                                             self.reference.as_slice());
+        let should_update = self.config.update_remotes() || actual_rev.is_err();
 
         let repo = if should_update {
             try!(self.config.shell().status("Updating",
@@ -168,11 +168,14 @@ impl<'a, 'b> Source for GitSource<'a, 'b> {
         } else {
             self.remote.db_at(&self.db_path)
         };
+        let actual_rev = match actual_rev {
+            Ok(rev) => rev,
+            Err(..) => try!(repo.rev_for(self.reference.as_slice())),
+        };
 
-        let checkout = try!(repo.copy_to(self.reference.as_slice(),
-                                         &self.checkout_path));
+        try!(repo.copy_to(actual_rev.clone(), &self.checkout_path));
 
-        let source_id = self.source_id.with_precise(checkout.get_rev().to_string());
+        let source_id = self.source_id.with_precise(actual_rev.to_string());
         let path_source = PathSource::new(&self.checkout_path, &source_id);
 
         self.path_source = Some(path_source);
@@ -191,7 +194,7 @@ impl<'a, 'b> Source for GitSource<'a, 'b> {
 
     fn fingerprint(&self, _pkg: &Package) -> CargoResult<String> {
         let db = self.remote.db_at(&self.db_path);
-        db.rev_for(self.reference.as_slice())
+        db.rev_for(self.reference.as_slice()).map(|r| r.to_string())
     }
 }
 
index 3a3a0a583e9e19fb15ff7b19e3bfb4de27f7b2be..4caab78801ddc854728b166c994d5d45499a995d 100644 (file)
@@ -13,6 +13,9 @@ pub enum GitReference {
     Other(String)
 }
 
+#[deriving(PartialEq,Clone,Encodable)]
+pub struct GitRevision(String);
+
 impl GitReference {
     pub fn for_str<S: Str>(string: S) -> GitReference {
         if string.as_slice() == "master" {
@@ -38,6 +41,18 @@ impl Show for GitReference {
     }
 }
 
+impl Str for GitRevision {
+    fn as_slice(&self) -> &str {
+        let GitRevision(ref me) = *self;
+        me.as_slice()
+    }
+}
+
+impl Show for GitRevision {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        self.as_slice().fmt(f)
+    }
+}
 
 macro_rules! git(
     ($config:expr, $($arg:expr),+) => (
@@ -104,15 +119,13 @@ impl<E, S: Encoder<E>> Encodable<S, E> for GitDatabase {
 pub struct GitCheckout {
     database: GitDatabase,
     location: Path,
-    reference: GitReference,
-    revision: String,
+    revision: GitRevision,
 }
 
 #[deriving(Encodable)]
 pub struct EncodableGitCheckout {
     database: GitDatabase,
     location: String,
-    reference: String,
     revision: String,
 }
 
@@ -121,7 +134,6 @@ impl<E, S: Encoder<E>> Encodable<S, E> for GitCheckout {
         EncodableGitCheckout {
             database: self.database.clone(),
             location: self.location.display().to_string(),
-            reference: self.reference.to_string(),
             revision: self.revision.to_string()
         }.encode(s)
     }
@@ -138,9 +150,9 @@ impl GitRemote {
         &self.location
     }
 
-    pub fn has_ref<S: Str>(&self, path: &Path, reference: S) -> CargoResult<()> {
-        git_output!(*path, "rev-parse", reference.as_slice());
-        Ok(())
+    pub fn rev_for<S: Str>(&self, path: &Path, reference: S)
+                           -> CargoResult<GitRevision> {
+        Ok(GitRevision(git_output!(*path, "rev-parse", reference.as_slice())))
     }
 
     pub fn checkout(&self, into: &Path) -> CargoResult<GitDatabase> {
@@ -177,34 +189,38 @@ impl GitDatabase {
         &self.path
     }
 
-    pub fn copy_to<S: Str>(&self, reference: S,
-                           dest: &Path) -> CargoResult<GitCheckout> {
+    pub fn copy_to(&self, rev: GitRevision, dest: &Path)
+                   -> CargoResult<GitCheckout> {
         let checkout = try!(GitCheckout::clone_into(dest, self.clone(),
-                                  GitReference::for_str(reference.as_slice())));
+                                                    rev.clone()));
 
-        if self.remote.has_ref(dest, reference.as_slice()).is_err() {
-            try!(checkout.fetch());
+        match self.remote.rev_for(dest, "HEAD") {
+            Ok(ref head) if rev == *head => return Ok(checkout),
+            _ => try!(checkout.fetch()),
         }
-        try!(checkout.reset(reference.as_slice()));
+
+        try!(checkout.reset());
         try!(checkout.update_submodules());
 
         Ok(checkout)
     }
 
-    pub fn rev_for<S: Str>(&self, reference: S) -> CargoResult<String> {
-        Ok(git_output!(self.path, "rev-parse", reference.as_slice()))
+    pub fn rev_for<S: Str>(&self, reference: S) -> CargoResult<GitRevision> {
+        self.remote.rev_for(&self.path, reference)
     }
 
+    pub fn has_ref<S: Str>(&self, reference: S) -> CargoResult<()> {
+        git_output!(self.path, "rev-parse", "--verify", reference.as_slice());
+        Ok(())
+    }
 }
 
 impl GitCheckout {
     fn clone_into(into: &Path, database: GitDatabase,
-                  reference: GitReference) -> CargoResult<GitCheckout> {
-        let revision = try!(database.rev_for(reference.as_slice()));
+                  revision: GitRevision) -> CargoResult<GitCheckout> {
         let checkout = GitCheckout {
             location: into.clone(),
             database: database,
-            reference: reference,
             revision: revision,
         };
 
@@ -241,6 +257,7 @@ impl GitCheckout {
 
         git!(dirname, "clone", "--no-checkout", "--quiet",
              self.get_source(), &self.location);
+        try!(self.reset());
 
         Ok(())
     }
@@ -270,8 +287,9 @@ impl GitCheckout {
         Ok(())
     }
 
-    fn reset(&self, revision: &str) -> CargoResult<()> {
-        Ok(git!(self.location, "reset", "-q", "--hard", revision))
+    fn reset(&self) -> CargoResult<()> {
+        Ok(git!(self.location, "reset", "-q", "--hard",
+                self.revision.as_slice()))
     }
 
     fn update_submodules(&self) -> CargoResult<()> {